package suncertify.application;

import java.io.File;
import java.text.NumberFormat;
import java.util.Locale;

import suncertify.application.exceptions.DatabaseFileNotFoundException;
import suncertify.application.exceptions.DatabaseFileNotReadableException;
import suncertify.application.exceptions.DatabaseFileNotWritableException;
import suncertify.application.exceptions.DatabaseFileWrongTypeException;
import suncertify.gui.DesktopGui;

/**
 * 
 * This class is the entry point for the client and server applications, it contains the <code>main()</code> method. The
 * class is abstract and is overridden by two sub classes, each sub class <code>ClientApplication</code> and
 * <code>ServerApplication</code> provide one of the two supported application types.
 * 
 * <p>
 * This class provides common information and functionality that is used by the client and server applications, e.g. the
 * application's locale and the user's current directory.
 * </p>
 * 
 * @see suncertify.application.ClientApplication#ClientApplication(ApplicationMode)
 * @see suncertify.application.ServerApplication#ServerApplication()
 * 
 */
public abstract class Application {

    /**
     * Constant that defines the maximum value of the TCP port number that is allowed for the client and for the server.
     */
    public static final int MAXIMUM_PORT_VALUE = 65000;

    /**
     * Constant that defines the minimum allowed port number value.
     */
    public static final int MINIMUM_PORT_VALUE = 1;

    /**
     * Constant that defines the <b>server</b> command line parameter, that represents the server mode.
     */
    private static final String ARGUMENT_SERVER = "server";

    /**
     * Constant that represents the <b>stand alone</b> command line parameter, that represents the client mode.
     */

    private static final String ARGUMENT_ALONE = "alone";

    /**
     * Constant that represents the database file name and extension.
     */
    private static final String DATABASE_FILE_NAME = "db-2x2.db";

    /**
     * Constant that represents the database file extension.
     */
    private static final String DATABASE_FILE_EXTENSION = ".db";

    /**
     * Entry point for the client or server applications. The main method initializes the application's <code>Log</code>
     * object. If the log object fails to initialize then a warning message is shown, the behavior of the application is
     * not affected by this, only the log messages will be lost and not saved to the file system.
     * 
     * The method accepts two command line parameters:
     * 
     * <ol>
     * <li><b>server:</b> This value starts the application in server mode.</li>
     * <li><b>alone:</b> This value starts the application in client stand alone mode.</li>
     * <li>If no command line parameter is provided then the client network mode is started.</li>
     * </ol>
     * 
     * <p>
     * Any other command line parameter will be ignored, the application will exit immediately, this will be logged in
     * the log file.
     * 
     * @param args
     *            Command line arguments passed to the application. The application only accepts one command line
     *            parameter. Allowed values are "alone" and "server".
     * 
     * @see suncertify.application.Log
     * @see suncertify.application.ApplicationMode
     * 
     */
    public static void main(String[] args) {

	try {
	    // initialize the application logger
	    Log.initializeLogger();
	} catch (Exception e) {
	    // inform the user that no log file will be available.
	    Log.logInfo("Log.initializeLogger() failed!");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_LOGGER_INITIALIZATION_ERROR);
	}

	// Check the command line parameters and determine the application's mode.
	if (args.length == 0) {

	    Log.logInfo("Application main() called without command line parameter.");
	    new ClientApplication(ApplicationMode.NETWORK_CLIENT).startApplication();

	} else if (args[0].equalsIgnoreCase(ARGUMENT_SERVER)) {

	    Log.logInfo("Application main() called with 'server' command line parameter.");
	    new ServerApplication().startApplication();

	} else if (args[0].equalsIgnoreCase(ARGUMENT_ALONE)) {

	    Log.logInfo("Application main() called with 'alone' command line parameter.");
	    new ClientApplication(ApplicationMode.STANDALONE_CLIENT).startApplication();

	} else {
	    Log
		    .logSevere("Application main() called with invalid argument(s)! allowed arguments are 'server' and 'alone'");
	}
    }

    /**
     * Abstract method implemented by the <code>ClientApplication and ServerApplication</code> sub classes. The
     * <code>main</code> method calls this method once.
     */
    abstract void startApplication();

    /**
     * Returns the full path to the application's database file. The path is read from the application's properties file
     * first. If the properties file does not exist or, the file exists but the database path value is not stored yet
     * then a path to the current user directory is returned.
     * 
     * Note, the path value is not validated by the function, if the path exists in the application's log file then it
     * is simply returned to the caller even if the path does not exist anymore or is invalid. It is assumed that only
     * the application trusts the values from the properties file and that no other application changes the value.
     * 
     * @return The path of the application's database file.
     * 
     * @see suncertify.application.UserConfiguration
     * @see java.lang.System#getProperty(String)
     */
    public String getDatabaseFilePath() {

	// Get the path from the configuration file.
	String path = UserConfiguration.getInstance().getProperty(UserConfiguration.PROP_DATABASE_LOCATION);

	// check if the path is null or empty
	if (path == null || path.isEmpty()) {

	    // return a path that points to the current user directory
	    File databaseFile = new File(System.getProperty(UserConfiguration.SYSTEMPROPERTY_USER_DIR),
		    DATABASE_FILE_NAME);

	    return databaseFile.getPath();

	} else {

	    // don't validate the value here, we assume that the suncertify.properties file is only
	    // modified by our application.
	    return path;
	}
    }

    /**
     * Returns the port number used by the client (in network mode) and server. The port number is read from the
     * properties file first, if the file does not exist or the value does not exist yet in the file then a default port
     * number defined by the JVM is returned.
     * 
     * <p>
     * Note: The port number is not validated after being read from the properties file. The application trusts the port
     * value stored in the properties file and assumes that only the application itself saves this value.
     * </p>
     * 
     * @return The port number.
     * 
     * @see java.rmi.registry.Registry#REGISTRY_PORT
     * 
     */
    public int getPortNumber() {

	// get the port number from the application's properties file.
	String portString = UserConfiguration.getInstance().getProperty(UserConfiguration.PROP_SERVER_PORT);

	// check if the value is null or empty. Null means that the value does not exist in the properties file.
	if (portString == null || portString.isEmpty()) {

	    // returns the default java rmi port.
	    return java.rmi.registry.Registry.REGISTRY_PORT;
	} else {

	    try {
		// parse and return the saved port number.
		return Integer.parseInt(portString);
	    } catch (NumberFormatException e) {
		return java.rmi.registry.Registry.REGISTRY_PORT;
	    }
	}
    }

    /**
     * Validates the given database file path.
     * 
     * @param databaseFilePath
     *            The database file path.
     * @return true if the database file path:
     *         <ol>
     *         <li>Exists in the file system.</li>
     *         <li>Has the correct extension.</li>
     *         <li>Is readable.</li>
     *         <li>Is Writable</li>
     *         </ol>
     *         returns false if one or more of the above conditions are not true.
     * @throws DatabaseFileNotFoundException
     *             Thrown if the file does not exist.
     * @throws DatabaseFileWrongTypeException
     *             Thrown if the file does not have the correct extension.
     * @throws DatabaseFileNotReadableException
     *             Thrown if the file is not readable.
     * @throws DatabaseFileNotWritableException
     *             Thrown if the file is not writable.
     */
    public boolean isDatabaseFileValid(String databaseFilePath) throws DatabaseFileNotFoundException,
	    DatabaseFileWrongTypeException, DatabaseFileNotReadableException, DatabaseFileNotWritableException {

	File file = new File(databaseFilePath);

	if (!file.exists()) {
	    throw new DatabaseFileNotFoundException(databaseFilePath, null);

	} else if (!file.getName().endsWith(DATABASE_FILE_EXTENSION)) {
	    throw new DatabaseFileWrongTypeException(databaseFilePath, null);

	} else if (!file.canRead()) {
	    throw new DatabaseFileNotReadableException(databaseFilePath, null);

	} else if (!file.canWrite()) {
	    throw new DatabaseFileNotWritableException(databaseFilePath, null);

	} else {
	    return true;
	}
    }

    /**
     * Validates the port number value.
     * 
     * @param portNumber
     *            The TCP port number to be validated.
     * @return True if the port number is larger or equal than {@link #MINIMUM_PORT_VALUE} and smaller or equal
     *         {@link #MAXIMUM_PORT_VALUE}. Returns false otherwise.
     */
    public boolean isPortNumberValid(int portNumber) {

	return (portNumber >= MINIMUM_PORT_VALUE && portNumber <= MAXIMUM_PORT_VALUE);
    }

    /**
     * Returns the application's locale. The application uses a constant English USA locale. The locale is used to
     * format currency and decimal values.
     * 
     * @return The application's locale.
     * 
     * @see java.util.Locale#US
     */
    public static Locale getApplicationLocale() {

	// in this version a constant locale is assumed.
	return Locale.US;
    }

    /**
     * Returns the decimal separator based on the US locale. Example the ',' character for the US locale.
     * 
     * @return the decimal separator.
     */
    public static String getLocaleDecimalSeparator() {

	// format 1000 as a currency value the second character from the right (1,000) is the decimal separator.
	NumberFormat nf = NumberFormat.getCurrencyInstance(getApplicationLocale());
	String formattedString = nf.format(1000);

	// return the ',' character in "1,000"
	return formattedString.substring(2, 3);
    }

    /**
     * Returns the user's current directory. The current directory is the directory from which the application is run.
     * 
     * @return The current directory.
     * 
     * @see java.lang.System#getProperty(String)
     */
    public String getUserCurrentDirectory() {

	return System.getProperty(UserConfiguration.SYSTEMPROPERTY_USER_DIR);
    }

    /**
     * This function is called by the implementors of <code>Application</code> class at the beginning of the
     * application. This function should be called only once. This function initializes the following:
     * <ol>
     * <li>Sets the default <code>Locale</code> of the application to US.</li>
     * <li>Sets the look and feel of the JVM to the native look and feel supported by the hosting operating system.</li>
     * <li>Initializes the <code>UserConfiguration</code> singleton object that used to read and save the application
     * settings. If the <code>UserConfiguration</code> fails to initialize then a warning message is shown to the user.
     * The functionality of the application is not affected by this, only the application settings like database file
     * path and port number will not be saved in the applications properties file.</li>
     * </ol>
     * 
     * @see java.util.Locale
     * @see suncertify.gui.DesktopGui#setUILookAndFeel()
     * @see suncertify.application.UserConfiguration
     */
    void initializeApplication() {

	// set the
	Locale.setDefault(Application.getApplicationLocale());

	Log.logFine("Set locale to US");

	// set UI look and feel
	DesktopGui.setUILookAndFeel();

	// initialize the user configuration object
	UserConfiguration userConfig = UserConfiguration.getInstance();

	// check if any user configuration messages must be shown to the user
	if (userConfig.getInitializationMessage() != null) {

	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, userConfig.getInitializationMessage());
	}
    }

    /**
     * Terminates the currently running JVM.
     * 
     * @see java.lang.System#exit(int)
     */
    public void terminateApplication() {

	Log.logInfo("Terminate application!");

	System.exit(0);
    }
}
